
#ifndef _OPENGLFRAME_
#define _OPENGLFRAME_

#include <QtOpenGL/QGLWidget>
#include <QtGui>

#include "../meshdef/cgaltypes.h"
#include "../enumtypes.h"
#include "../meshdef/meshWithSkeleton.h"
#include "../meshdef/skeleton.h"
#include "../meshdef/skinning.h"
#include "../meshdef/clustering.h"

#include "../arcball.h"
//~ #include "../texture.h"
#include "../selection.h"

#include <vector>
#include <map>
#include <algorithm>

/// Interaction modes
enum InteractionMode{SELECT_MODE, EDITSKEL_MODE};
enum SkelEditOperation{NONE, JOINNEW, JOINMOVE, BONENEW};

/// A wrapper over QGLWidget.
/// Exhibits a collection of drawable instances.
class OpenGLFrame: public QGLWidget {
   
   Q_OBJECT
   
public:
   /// Constructor.
   OpenGLFrame (QWidget* parent=0, const QGLWidget* shareWidget = 0, Qt::WFlags f=0);
   ~OpenGLFrame();
   
   /// Sets the interaction mode
   void setInteractionMode (InteractionMode m);
   
protected:

public slots:
   
   /// Zoom in operation.
   void zoomIn ();

   /// Zoom out operation.
   void zoomOut ();

   /// Zoom All operation.
   void zoomAll ();

protected:

   /// Sets the interaction mode
   InteractionMode                       interactionMode;  
   
   /// Position of 3D cursor
   Point cursor3D;
   
   /// Scene tranformation
   Transformation sceneTransform;
   
   /// Temp transformations used for mouse tracking
   Transformation objectToScene, sceneToObject;
   
   /// Temp vectors used for mouse tracking
   Vector pixelDisplacement[2];
   Point grabStart;
   
   /// OpenGL initialization.
   void initializeGL();
   
   /// Drawing callback.
   void paintGL();
   
   /// Resize callback.
   /// @param width the width of the new window.
   /// @param height the height of the new window.
   void resizeGL(int width, int height);
   
   /// Mouse callback functions
   void mousePressEvent (QMouseEvent *event);
   void mouseMoveEvent (QMouseEvent *event);
   void mouseReleaseEvent (QMouseEvent *event);
   void wheelEvent(QWheelEvent *e);
   void keyPressEvent(QKeyEvent *e);
   void contextMenuEvent(QContextMenuEvent *e);
   
   /// Places the 3D cursor at a point in world space which is inferred from 
   /// 2D window coordinates.
   void setCursor (int x, int y);
   
   /// An arcball object
   ArcBall                arcball;
   float                  tM[16];   // transformation matrix
   
    /// //////////////////////////////////////////////////////////////////////////////////////
public:
   
   void setGLview(); /// It sets the projection/modelview matrices
   
   void meshRenderMode(MeshRenderMode mrm)         { meshrendermode_mask = mrm; }
   void meshRigSkinMode(RigSkinMask mask)          { rigskin_mask = mask; }
   void viewItemOn(ViewItems v)                    { viewitems_mask = viewitems_mask | v; } // inclusive-OR
   void viewItemOff(ViewItems v)                   { viewitems_mask = viewitems_mask ^ v; } // exclusive-or
   void lightingOnOff(bool l)                      { is_lightingOn = l; }
   bool isProjectionOrtho()                        { return isProjOrtho; }
   void orthographic(bool o) { 
      (o)?(isProjOrtho=true):(isProjOrtho=false);
   }
   
   /// Loads a new polyhedron from an off file. 
   /// @param filename name of file in off format.
   /// @return true if polyhedron loading was successful.
   bool loadShapeOFF(const QString& filename);
   
   /// Saves a new polyhedron on a file. 
   /// @param filename name of file in off format.
   /// @return true if polyhedron saving was successful.
   bool saveShapeOFF(const QString& filename);
   
   /// Saves a new skeleton on a file. 
   /// @param filename name of file in skel format.
   /// @return true if skeleton saving was successful.
   bool saveAsSkeleton(const QString& filename);
   bool loadSkeleton(const QString& filename);
   
   bool loadSkinning(const QString& filename);
   bool saveSkinning(const QString& filename);
   
   uint nvertices() const { if (model==0) return 0; return model->nvertices(); }
   
   void setSkinningDifussionAmplitude(double d)     { skinner.set_diffusion_amplitude(d); }
   void setSkinningNumberIterations(unsigned int i) { skinner.set_number_iterations(i); }
   void setSkinningNumberExpansions(unsigned int i) { skinner.set_number_expansions(i); }
   
   void bindSkeleton() { skinner.execute(model); }
   
   // Performs clustering of the mesh using the k-centers algorithm
   void meshClustering(unsigned int kcenters);
   
   // Performs clustering of the mesh using the bones/joints of a skeleton
   void meshClustering(bool usingbones);
   
   /*
   void meshClustering(unsigned int k) {
      if (model->skeleton == 0) model->clusterKcenters(k); 
      else {
         MLS::Clustering clusterer(model);
         clusterer.execute();
      }
      //~ else skinner.riggingControl(model);
   }
   */
   
   void viewClosestJoint(bool f) { isViewClosestJoint = f; }
   void viewAssociations(bool f) { isViewAssociations = f; }
   
   void setIsViewBoneVertices(bool f)           { isViewBoneVertices = f; }
   void setIsViewVertexBones(bool f)            { isViewVertexBones = f; }
   
   void setIsViewVertexJoints(bool f)           { isViewVertexJoints = f; }
   void setIsViewVertexJoint(bool f)            { isViewVertexJoint = f; }
   void setIsViewJointVertices(bool f)          { isViewJointVertices = f; }
   void setIsViewJointVertice(bool f)           { isViewJointVertice = f; }
   
   void setIsViewClusters(bool f)               { isViewClusters = f; }
   void setClusterUsingBones(bool f)            { clustering_using_bones = f; }
   
   void setLapFilterAlpha(float f)              { model->setLapFilterAlpha(f); };
   
private:
   void genWorldLimits();
   
   void drawFloor();
   // This function is from http://www.devmaster.net community.
   void glShadowProjection(float *l, float *e, float *n);
   
   Vector getTranslationWorld(int x, int y, int xlast, int ylast);
   Point getPointWorld(int x, int y);
   
private:
   bool                    is_lightingOn;
   bool                    isProjOrtho;     // true when projection is perspective. false when perspective
   int                     meshrendermode_mask;
   int                     rigskin_mask;
   unsigned                viewitems_mask;
   Selection               selection;       // the performer of selection operations
   int                     idSelected;      // selected object id

   bool                    isViewClosestJoint;
   bool                    isViewAssociations;
   bool                    isViewBoneVertices;
   bool                    isViewVertexBones;
   bool                    isViewJointVertices;
   bool                    isViewJointVertice;
   bool                    isViewVertexJoints;
   bool                    isViewVertexJoint;
   bool                    isViewClusters;
   
   bool                    clustering_using_bones;
   
   /// opengl data
   float                   fovy;         // Specifies the field of view angle, in degrees, in the y direction
   float                   zoomFactor;   // zoom factor
   float                   cameye[3];    // camera's eye
   float                   camcenter[3]; // Specifies the position of the reference point
   float                   mincoo[3];    // minimum point coordinates of the GL world
   float                   maxcoo[3];    // maximum point coordinates of the GL world
   
   /// time info
   float                   cfps;
   float                   lastTime;
   time_t                  start, end;
   unsigned                frame;
   
   GLdouble                mMtx[16], pMtx[16];  /// modelview/projection matrix
   GLint                   viewport[4];         /// the viewport
   int                     xini, yini;          ///< initial mouse position in window coord
   int                     xlast, ylast;        ///< last mouse position in window coord
   
   MeshWithSkeleton   *model;
   //~ Skeleton                        *skeleton;
   Skinning           skinner;
   
   int                    idNewJoinSource;
   Point                  pNewJoinSource;  // for drawing purpose 
   Point                  pNewJoinTarget;  // for drawing purpose 
   SkelEditOperation      skelEditOperation;
   
private slots:
   
   /// Sets the source of a bone using a joint
   void newSkelBoneSourceJoint();
   
   void newSkelJoint();
   void moveSkelJoint();
   void deleteSkelJoint();
   void newSkelBone(int idJoint, int idJointPrev);
   
   //~ void bindSkelBoneVertices();
   void splitSkelBone();
   void deleteSkelBone();
   void flipSkelBone();
};

#endif
